package com.onefinger.aspect;

import java.lang.reflect.Method;
import com.opensymphony.oscache.general.GeneralCacheAdministrator;
import com.opensymphony.oscache.web.filter.ExpiresRefreshPolicy;
import com.opensymphony.oscache.base.NeedsRefreshException;
import java.util.Properties;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.aspectj.lang.*;

@org.aspectj.lang.annotation.Aspect
public class CacheAspect {
	
	private final int	 NO_EXPIRE	 = 0;
	private final String CACHE_KEY	 = "cacheKey";
	private final String EXPIRE_TIME = "expireTime";
	@PersistenceContext
	private EntityManager em;
	
	private GeneralCacheAdministrator cache;
	
	public CacheAspect(com.opensymphony.oscache.general.GeneralCacheAdministrator cache){
		this.cache = cache;
		this.cache.destroy();
	}
	
	@org.aspectj.lang.annotation.Around("@annotation(Cache)")
	public synchronized Object cachear(org.aspectj.lang.ProceedingJoinPoint call) throws Throwable {
		Properties cacheProps = this.getAnnotationProperties(call, true);
	    Object	 result		  = null;
	    String	 cacheKey	  = cacheProps.getProperty(CACHE_KEY);
	    int		 expire		  = NO_EXPIRE;

	    expire	 = Integer.parseInt(cacheProps.getProperty(EXPIRE_TIME));
    	if (expire < 0){
    		expire = NO_EXPIRE;
    	}
	    
	    try {  
	    	 result  = cache.getFromCache(cacheKey);
	    } catch (NeedsRefreshException e) {
	    	 // Los datos de la cache no existen o han caducado
	    	cache.cancelUpdate(cacheKey);  
	    }  	    
	   
	    if (result == null) {
	    	result = call.proceed();
	    	
	    	if (expire == 0){
	    		cache.putInCache(cacheKey, result);	
	    	} else {
	    		cache.putInCache(cacheKey, result, new ExpiresRefreshPolicy(expire));
	    	}
		} else {
			System.out.println("From Cache: " + cacheKey);
		}
		
		return result;
	}
	
	@org.aspectj.lang.annotation.Around("@annotation(Uncache)")
	public synchronized Object descachear(org.aspectj.lang.ProceedingJoinPoint call) throws Throwable {
		Object result	 = call.proceed();
		
		Properties cacheProps = this.getAnnotationProperties(call, false);
		String	  cacheKey	  = cacheProps.getProperty(CACHE_KEY);		
		cache.removeEntry(cacheKey);
		System.out.println("Uncache: " + cacheKey);

		return result;
	}	

	
	/**
	 * @return Devuelve un properties con los atributos de la anotacion (Cachea o Descachea)
	 */	
	private Properties getAnnotationProperties(org.aspectj.lang.ProceedingJoinPoint call, boolean isCacheo) {
		Properties	properties = new Properties();
		Method		metodo	   = this.getCallMethod(call);
		
		if (isCacheo){
			Cache	anotacion  = metodo.getAnnotation(Cache.class);
			properties.put(CACHE_KEY, anotacion.cacheKey());
			properties.put(EXPIRE_TIME, String.valueOf(anotacion.expireTime()));
		} else {
			Uncache anotacion = metodo.getAnnotation(Uncache.class);
			properties.put(CACHE_KEY, anotacion.cacheKey());
		}
		
		return properties;
	}

	
	/**
	 * @return Devuelve una referencia al metodo invocado por AOP
	 */
	@SuppressWarnings("unchecked")
	private Method getCallMethod(org.aspectj.lang.ProceedingJoinPoint call){
		Method metodo	  =  null;
		
		try {
			Signature sig     = call.getSignature();
			Class  clase	  = sig.getDeclaringType();
			String methodName = sig.getName();
			
			Method[] declaredMethods = clase.getDeclaredMethods();
			for( int idx = 0; idx < declaredMethods.length; idx++ )
			{
				if( ((Method)declaredMethods[idx]).getName().equals( methodName ) )
				{
					metodo = ((Method)declaredMethods[idx]);
					break;
				}
			}
			
			if (metodo.getDeclaringClass().isInterface()) {     
				metodo = call.getTarget().getClass().getDeclaredMethod(methodName, metodo.getParameterTypes());     
			} 
			
		} catch (Exception e) {
			// Ignoramos: SecurityException, NoSuchMethodException
			e.printStackTrace();
		}
		
		return metodo;
	}	
}
